﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE;
using System.Reflection;
using AutomationFoundation.CodeToolKit;
using Microsoft.Samples.LinqToCodeModel.Extensions;
using Microsoft.Samples.LinqToCodeModel;
namespace MVC4WPF.Automation.Data
{

    /// <summary>
    /// Helper class that will break down a interface contract into its individual pieces and return the individual items information. 
    /// This is intended to be used with automation templates, or a templating process.
    /// </summary>
    public static class ContractBuilder
    {
        /// <summary>
        /// Creates the based data used to format contract implementations.
        /// </summary>
        /// <param name="contract">The type of contract to create</param>
        /// <returns>All the contract data used with template automation.</returns>
        public static ContractInformation BuildContract(ContractType contract)
        {
            ContractInformation formattedContract = null;

            if (contract != null)
            {
                if (contract.IsType)
                {
                    formattedContract = BuildContractFromType(contract.TypeForContract);
                }
                else
                {
                    formattedContract = BuildContractFromRawCode(contract.RawInterface);
                }
            }
            return formattedContract;
        }

        /// <summary>
        /// Creates the contraction information from the type information of the contract.
        /// </summary>
        /// <param name="contractType">The reflected type information about the contract.</param>
        /// <returns>All the contract data used with template automation.</returns>
        private static ContractInformation BuildContractFromType(Type contractType)
        {
            if (contractType != null)
            {
                ContractInformation contract = new ContractInformation
                {
                    Name = contractType.Name,
                    FullName = contractType.GetTypeNameCSharp(true),
                    Namespace = contractType.Namespace,
                    Properties = BuildContractPropertiesFromType(contractType),
                    Events = BuildContractEventsFromType(contractType),
                    Methods = BuildContractMethodsFromType(contractType)
                };

                return contract;
            }
            return null;
        }

        /// <summary>
        /// Getting the property data for each property included within this type.
        /// </summary>
        /// <param name="contractType">The type the parameters are being extracted from.</param>
        /// <returns>List of contractproperties, if no propertys are found a empty list will be returned.</returns>
        private static List<ContractProperty> BuildContractPropertiesFromType(Type contractType)
        {
            PropertyInfo[] properties = contractType.GetProperties();

            List<ContractProperty> propertyList = new List<ContractProperty>();

            ContractProperty propertyInfo = null;

            Type propertyBag = null;

            if (contractType == null)
            {
                return propertyList;
            }

            propertyBag = ContainsPropertyBag(contractType, true, false);

            if (propertyBag != null)
            {
                propertyInfo = new ContractProperty
                {
                    Name = "ViewPropertyBag",
                    PropertyType = propertyBag.GetGenericArguments()[0].GetTypeNameCSharp(true),
                    PropertyAccessLevel = "public",
                    TypeIsDisposable = false,
                    TypeIsObject = true,
                    IsGetPublic = true,
                    IsSetPublic = true,
                    HasGetAccessor = true,
                    HasSetAccessor = true
                };
                propertyList.Add(propertyInfo);
            }
            else
            {
                propertyBag = ContainsPropertyBag(contractType, false, true);
                if (propertyBag != null)
                {
                    propertyInfo = new ContractProperty
                    {
                        Name = "ControllerPropertyBag",
                        PropertyType = propertyBag.GetGenericArguments()[0].GetTypeNameCSharp(true),
                        PropertyAccessLevel = "public",
                        TypeIsDisposable = false,
                        TypeIsObject = true,
                        IsGetPublic = true,
                        IsSetPublic = true,
                        HasGetAccessor = true,
                        HasSetAccessor = true
                    };
                    propertyList.Add(propertyInfo);
                }
                else
                {
                    propertyBag = ContainsPropertyBag(contractType, false, false);
                    if (propertyBag != null)
                    {
                        propertyInfo = new ContractProperty
                        {
                            Name = "ModelPropertyBag",
                            PropertyType = propertyBag.GetGenericArguments()[0].GetTypeNameCSharp(true),
                            PropertyAccessLevel = "public",
                            TypeIsDisposable = false,
                            TypeIsObject = true,
                            IsGetPublic = true,
                            IsSetPublic = true,
                            HasGetAccessor = true,
                            HasSetAccessor = true
                        };
                        propertyList.Add(propertyInfo);
                    }
                }
            }

            foreach (var item in properties)
            {
                propertyInfo = new ContractProperty
                {
                    Name = item.Name,
                    PropertyType = item.PropertyType.GetTypeNameCSharp(true),
                    HasGetAccessor = item.CanRead,
                    HasSetAccessor = item.CanWrite
                };

                if (propertyInfo.HasGetAccessor)
                {
                    propertyInfo.GetAccessLevel = item.GetGetAccessLevelCSharp();

                    if (propertyInfo.GetAccessLevel == "public")
                    {
                        propertyInfo.IsGetPublic = true;
                    }
                    else
                    {
                        propertyInfo.IsGetPublic = false;
                    }
                }

                if (propertyInfo.HasSetAccessor)
                {
                    propertyInfo.SetAccessLevel = item.GetSetAccessLevelCSharp();

                    if (propertyInfo.SetAccessLevel == "public")
                    {
                        propertyInfo.IsSetPublic = true;
                    }
                    else
                    {
                        propertyInfo.IsSetPublic = false;
                    }
                }

                if (!item.PropertyType.IsValueType)
                {
                    propertyInfo.TypeIsObject = true;
                }
                else
                {
                    propertyInfo.TypeIsObject = false;
                }

                if (item.PropertyType.GetInterfaces().Contains(typeof(IDisposable)))
                {
                    propertyInfo.TypeIsDisposable = true;
                }
                else
                {
                    propertyInfo.TypeIsDisposable = false;
                }

                propertyList.Add(propertyInfo);
            }

            return propertyList;
        }

        /// <summary>
        /// Helper method that determins if a IPropertyBag generic interface has been implemented with this contract.
        /// </summary>
        /// <param name="contractType">The contract interface to check for the property bag.</param>
        /// <param name="viewPropertyBag">Flag to determine if you are checking for a view property bag.</param>
        /// <returns>The reflected type of the property bag, or null if no type is found.</returns>
        private static Type ContainsPropertyBag(Type contractType, bool viewPropertyBag, bool controllerPropertyBag)
        {
            Type[] interfaces = contractType.GetInterfaces();

            if (interfaces != null)
            {
                foreach (var item in interfaces)
                {
                    if (viewPropertyBag)
                    {
                        if (item.Name.StartsWith("IViewPropertyBag"))
                        {
                            return item;
                        }
                    }
                    else if (controllerPropertyBag)
                    {
                        if (item.Name.StartsWith("IControllerPropertyBag"))
                        {
                            return item;
                        }
                    }
                    else
                    {
                        if (item.Name.StartsWith("IModelPropertyBag"))
                        {
                            return item;
                        }
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Creates a defintion of event data for each event in the type.
        /// </summary>
        /// <param name="contractType">The reflected contract interface to search.</param>
        /// <returns>The list of ContractEvent objects, or an empty list if no events were found.</returns>
        private static List<ContractEvent> BuildContractEventsFromType(Type contractType)
        {
            List<ContractEvent> events = new List<ContractEvent>();

            if (contractType == null)
            {
                return events;
            }

            EventInfo[] eventData = contractType.GetEvents();

            if (eventData != null)
            {
                foreach (var item in eventData)
                {
                    ContractEvent eventDetail = new ContractEvent
                    {
                        Name = item.Name,
                        EventAccessLevel = item.GetEventAccessCSharp(),
                        EventHandler = item.EventHandlerType.GetTypeNameCSharp(true),
                        DelegateParameters = item.GetDelegateParameters(true)
                    };

                    events.Add(eventDetail);
                }
            }

            return events;
        }

        /// <summary>
        /// Builds all the method contract information
        /// </summary>
        /// <param name="contractType">Reflected contract information used to build the method list.</param>
        /// <returns>List of method contract information classes, or an empty list if no methods were found.</returns>
        private static List<ContractMethod> BuildContractMethodsFromType(Type contractType)
        {
            List<ContractMethod> methods = new List<ContractMethod>();
            List<string> blockList = new List<string>();
            if (contractType == null)
            {
                return methods;
            }

            PropertyInfo[] properties = contractType.GetProperties(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

            contractType.GetMethods();
            if (properties != null)
            {
                foreach (var propInfo in properties)
                {
                    blockList.Add(string.Format("get_{0}", propInfo.Name));
                    blockList.Add(string.Format("set_{0}", propInfo.Name));
                }

            }

            EventInfo[] events = contractType.GetEvents(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

            contractType.GetMethods();
            if (properties != null)
            {
                foreach (var eventInfo in events)
                {
                    blockList.Add(string.Format("add_{0}", eventInfo.Name));
                    blockList.Add(string.Format("remove_{0}", eventInfo.Name));
                }

            }

            MethodInfo[] methodData = contractType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

            if (methodData != null)
            {
                foreach (var item in methodData)
                {
                    if (!blockList.Contains(item.Name))
                    {
                        ContractMethod methodDetail = new ContractMethod
                        {
                            AcessLevel = item.GetMethodAccessCSharp(),
                            Name = item.GetMethodName(true),
                            Parameters = item.GetParametersCSharp(true),
                            ReturnType = item.GetReturnTypeCSharp(true)
                        };

                        methods.Add(methodDetail);
                    }
                }
            }
            return methods;
        }

        /// <summary>
        /// Builds out the contract information directly from codeinterface data from visual studio.
        /// </summary>
        /// <param name="rawContract">The CodeInterface obeject in code that contains the contract definition.</param>
        /// <returns>The contract information.</returns>
        private static ContractInformation BuildContractFromRawCode(CodeInterface rawContract)
        {
            return null;
        }

    }
}
